/**
 * 
 */
package com.ztesoft.core.db.ds;

import java.sql.SQLException;
import java.util.Iterator;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import javax.sql.DataSource;

import org.apache.commons.dbcp.BasicDataSource;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;

import com.alibaba.druid.filter.config.ConfigFilter;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import com.ztesoft.framework.exception.ExceptionHandler;
import com.ztesoft.framework.exception.SystemErrorCode;
import com.ztesoft.framework.log.ZTEsoftLogManager;
import com.ztesoft.web.common.Configuration;
import com.ztesoft.web.common.DMSConstant;

/**
 * <Description>动态数据源管理类，可根据配置动态创建DHCP数据源连接池，也可以关闭连接池 <br>
 * 
 * @author pan.xiaobo<br>
 * @version 1.0<br>
 * @taskId <br>
 * @CreateDate 2015年8月4日 <br>
 * @since V1.0<br>
 * @see com.ztesoft.core.db.ds <br>
 */

@Service("dynamicDataSourceManager")
public class DynamicDataSourceManager {

    private static final ZTEsoftLogManager logger = ZTEsoftLogManager
            .getLogger(DynamicDataSourceManager.class);

    private static final ConcurrentMap<String, javax.sql.DataSource> dataSourceMap = 
    		new ConcurrentHashMap<String, javax.sql.DataSource>();

    private ConcurrentMap<String, javax.sql.DataSource> getDataSourceMap() {
        return this.dataSourceMap;
    }

    private void validate(DbcpConfig dbcpConfig) {
        Assert.notNull(dbcpConfig, "dbcpConfig must not be null.");
        Assert.notNull(dbcpConfig.getDbtype(), "dbtype must not be null.");
        Assert.notNull(dbcpConfig.getDriverClassName(),
                "driverClassName must not be null.");
        Assert.notNull(dbcpConfig.getUrl(), "url must not be null.");
        Assert.notNull(dbcpConfig.getUsername(), "username must not be null.");
        Assert.notNull(dbcpConfig.getPassword(), "password must not be null.");
    }

    /**
     * 创建数据源连接池
     * 
     * @param poolId 连接池唯一标识，poolId
     * @param dbcpConfig 连接池的配置信息类
     */
    public synchronized void createDataSourcePool(String poolId,
            DbcpConfig dbcpConfig) {
    	validate(dbcpConfig);
        BasicDataSource basicDataSource = new BasicDataSource();

        basicDataSource.setDriverClassName(dbcpConfig.getDriverClassName());
        basicDataSource.setUrl(dbcpConfig.getUrl());
        basicDataSource.setUsername(dbcpConfig.getUsername());
        basicDataSource.setPassword(dbcpConfig.getPassword());

        basicDataSource.setDefaultAutoCommit(dbcpConfig.isDefaultAutoCommit());
        basicDataSource.setDefaultReadOnly(dbcpConfig.isDefaultReadOnly());

        //
        basicDataSource.setMaxActive(dbcpConfig.getMaxActive());
        basicDataSource.setMaxIdle(dbcpConfig.getMaxIdle());
        basicDataSource.setMinIdle(dbcpConfig.getMinIdle());
        basicDataSource.setMaxWait(dbcpConfig.getMaxWait());

        basicDataSource.setInitialSize(dbcpConfig.getInitialSize());
        basicDataSource.setValidationQuery(dbcpConfig.getValidationQuery());

        basicDataSource.setRemoveAbandoned(dbcpConfig.isRemoveAbandoned());
        basicDataSource.setRemoveAbandonedTimeout(dbcpConfig
                .getRemoveAbandonedTimeout());
        basicDataSource.setLogAbandoned(dbcpConfig.isLogAbandoned());

        basicDataSource.setMinEvictableIdleTimeMillis(dbcpConfig
                .getMinEvictableIdleTimeMillis());
        basicDataSource.setTimeBetweenEvictionRunsMillis(dbcpConfig
                .getTimeBetweenEvictionRunsMillis());

        basicDataSource.setTestOnBorrow(dbcpConfig.isTestOnBorrow());
        basicDataSource.setTestOnReturn(dbcpConfig.isTestOnReturn());
        basicDataSource.setTestWhileIdle(dbcpConfig.isTestWhileIdle());

        basicDataSource.setNumTestsPerEvictionRun(dbcpConfig
                .getNumTestsPerEvictionRun());

        getDataSourceMap().put(poolId, basicDataSource);
    }

    /**
     * 获取数据源，如果不存在，则根据dbcpConfig配置创建
     * 
     * @param poolId
     * @param dbcpConfig
     * @return
     * @throws Exception
     */
    public synchronized DataSource getDataSourcePoolByPoolId(
            String poolId, DbcpConfig dbcpConfig) throws Exception {
        // 先获取
        DataSource basicDataSource = getDataSourceMap().get(poolId);
        if (basicDataSource == null) {
            logger.info(String
                    .format("Could not found datasource pool by this poolId[poolId=%s],created it now... ",
                            poolId));
            // 未获取到相应的数据源，则创建新的数据源连接池
            // add
            createDataSourcePool(poolId, dbcpConfig);
            // get
            basicDataSource = getDataSourceMap().get(poolId);

        }
        else {
            logger.info(String.format(
                    "Found datasource pool by this poolId[poolId=%s]!", poolId));
        }
        return basicDataSource;
    }

    /**
     * @param poolId
     * @return
     * @throws Exception
     */
    public synchronized DataSource getDataSourcePoolByPoolId(String poolId)
            throws Exception {
        // 先获取
        DataSource basicDataSource = getDataSourceMap().get(poolId);
        if (basicDataSource == null) {
            logger.error(String
                    .format("Could not found datasource pool by this poolId[poolId=%s]!Throw Exception...",
                            poolId));
            throw ExceptionHandler
                    .publish(
                            SystemErrorCode.LOOKUP_DATASOURCE_ERROR,
                            String.format(
                                    "Could not found this datasource pool by this poolId[poolId=%s]!Please created it first!",
                                    poolId));

        }
        logger.info(String.format(
                "Found datasource pool by this poolId[poolId=%s]!", poolId));
        return basicDataSource;
    }

    public synchronized void close(String poolId) throws SQLException {
        logger.debug(String.format(
                "Close datasource pool by poolId[poolId=%s]!", poolId));
        BasicDataSource basicDataSource = (BasicDataSource) getDataSourceMap().get(poolId);
        if (basicDataSource == null) {
            logger.error(String
                    .format("Close pool,Could not found datasource pool by this poolId[poolId=%s],skip close it...",
                            poolId));
            return;
        }
        basicDataSource.close();
        dataSourceMap.remove(poolId);
    }

    /**
     * 关闭所有数据源连接池
     */
    public synchronized void closeAll() {
        logger.debug(String.format("Close all datasource pool!"));
        Set<String> key = dataSourceMap.keySet();
        for (Iterator<String> it = key.iterator(); it.hasNext();) {
            String poolId = it.next();
            BasicDataSource basicDataSource = (BasicDataSource) dataSourceMap.get(poolId);
            try {
                basicDataSource.close();
                dataSourceMap.remove(poolId);
            }
            catch (Exception e) {
                logger.error(String.format(
                        "CloseAll Pool cast Exception [poolId=%s]",
                        ToStringBuilder.reflectionToString(basicDataSource)));
                e.printStackTrace();
            }
        }
    }

    /**
     * 是否存在某个连接池
     * 
     * @param poolId
     * @return
     */
    public synchronized boolean isPoolExist(String poolId) {
        DataSource basicDataSource = getDataSourceMap().get(poolId);
        if (basicDataSource == null) {
            return false;
        }
        return true;
    }

    /**
     * 获取连接池的个数
     * 
     * @return
     */
    public synchronized int getPoolSize() {
        return getDataSourceMap().size();
    }
    
    /**
     * 
     * @param dbLinkMsg	数据库链接信息 	{dbType,url,usr,pwd}
     * @return
     */
    public DbcpConfig bulidDbcpConfig(final String[] dbLinkMsg) {
        DbcpConfig cbcpConfig = new DbcpConfig(dbLinkMsg[0]);
        cbcpConfig.setUrl(dbLinkMsg[1]);
        cbcpConfig.setUsername(dbLinkMsg[2]);
        cbcpConfig.setPassword(dbLinkMsg[3]);
        //连接池内连接空闲超过此毫秒数就可能被回收
        cbcpConfig.setMinEvictableIdleTimeMillis(30L*60*1000);
        //连接池多久执行一次扫描（剔除超时空闲连接）
        cbcpConfig.setTimeBetweenEvictionRunsMillis(10L*60*1000);
        return cbcpConfig;
    }

    /**
     * --------
     * @param poolId
     * @param dbType
     * @param dbLinkMsg
     * @return
     * @throws Exception
     */
    public synchronized DataSource getDsPoolByPoolId(
            String poolId, final String[] dbLinkMsg) {
        // 先获取
        DataSource basicDataSource = getDataSourceMap().get(poolId);
        if (basicDataSource == null) {
            logger.info(String
                    .format("Could not found datasource pool by this poolId[poolId=%s],created it now... ",
                            poolId));
            // 未获取到相应的数据源，则创建新的数据源连接池
            // add
            createDataSourcePool(poolId, bulidDbcpConfig(dbLinkMsg));
            // get
            basicDataSource = getDataSourceMap().get(poolId);

        }
        else {
            logger.info(String.format("Found datasource pool by this poolId[poolId=%s]!", poolId));
        }
        return basicDataSource;
    }
    
    
    
    /**
     * 于appconfig.properties 配置USE_DRUID是否用druid数据源，否则用dbcp数据源
     * @param poolId
     * @param dbLinkMsg
     * @return DataSource
     */
    public synchronized DataSource getDruidDataSource(
            String poolId, final String[] dbLinkMsg) {
    	
    	if (getDataSourceMap().get(poolId) == null) {
        	if (DMSConstant.IS_USE_DRUID) {//druid
        		ConfigFilter configFilter = new ConfigFilter();
        		Properties info = configFilter.loadConfig("config/props/druidconfig.properties");
        		try {
        			DruidDataSource dataSource = (DruidDataSource) DruidDataSourceFactory.createDataSource(info);
        			setDefaultProperties(dbLinkMsg[0], dataSource);
//        			if (dbLinkMsg[0].equals("oracle")) {//如果是oracle该数字配置大些
//        				dataSource.setMaxOpenPreparedStatements(100);
//        			}
        			dataSource.setUrl(dbLinkMsg[1]);
        			dataSource.setUsername(dbLinkMsg[2]);
        			dataSource.setPassword(dbLinkMsg[3]);
        			getDataSourceMap().put(poolId, dataSource);
        		} catch (Exception ignore) {
        		}
        	} else {//dbcp
                createDataSourcePool(poolId, bulidDbcpConfig(dbLinkMsg));
        	}
    	}
        return getDataSourceMap().get(poolId);
    }

    private void setDefaultProperties(String typeName, DruidDataSource dataSource) {
    	DBType dbType = DBType.getInstance(typeName);
        switch (dbType) {
            case MYSQL:
            	dataSource.setValidationQuery("SELECT 1 ");
            	dataSource.setDriverClassName("com.mysql.jdbc.Driver");
                break;
            case ORACLE:
            	dataSource.setValidationQuery("SELECT 1 FROM DUAL ");
            	dataSource.setDriverClassName("oracle.jdbc.driver.OracleDriver");
                break;
            case INFORMIX:
            	dataSource.setValidationQuery("SELECT 1 FROM systables ");
            	dataSource.setDriverClassName("com.informix.jdbc.IfxDriver");
                break;
            case TIMESTEN:
                break;
            case MDB:
                break;
            case DB2:
            	dataSource.setValidationQuery("select 1 from sysibm.sysdummy1 ");
            	dataSource.setDriverClassName("com.ibm.db2.jcc.DB2Driver");
                break;
            case SQLSERVER2000:
            	dataSource.setValidationQuery("SELECT 1 ");
            	dataSource.setDriverClassName("com.microsoft.jdbc.sqlserver.SQLServerDriver");
                break;
            case SQLSERVER2005:
            	dataSource.setValidationQuery("SELECT 1 ");
                dataSource.setDriverClassName("com.microsoft.sqlserver.jdbc.SQLServerDriver");
                break;

        }
    }
}
